package com.jjsc.product.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jjsc.common.core.utils.uuid.UUID;
import com.jjsc.product.api.domain.Product;
import com.jjsc.product.api.domain.ProductSku;
import com.jjsc.product.mapper.ProductSkuMapper;
import com.jjsc.product.service.IProductService;
import com.jjsc.product.service.IProductSkuService;
import com.jjsc.product.vo.ProductSkuVo;
import com.jjsc.product.api.domain.skuPriceVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author 41254
 * @description 针对表【product_sku(商品sku表)】的数据库操作Service实现
 * @createDate 2025-10-20 17:08:20
 */
@Slf4j
@Service
public class ProductSkuServiceImpl extends ServiceImpl<ProductSkuMapper, ProductSku>
        implements IProductSkuService {

    @Autowired
    private IProductService productService;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 获取商品sku销量前10的列表
     */
    @Override
    public List<ProductSkuVo> getTopTenProductSkuList() {
        return baseMapper.getTopTenProductSkuList();
    }

    /**
     * 获取2级分类下的商品Sku列表
     */
    @Override
    public List<ProductSkuVo> getProductSkuListByCategory2Id(Long category2Id) {
        List<Product> productList = productService.getProductListByCategory2Id(category2Id);
        List<ProductSkuVo> productSkuVoList = new ArrayList<>();

        for (Product product : productList) {
            // 为每个商品查询其所有SKU
            List<ProductSku> productSkus = baseMapper
                    .selectList(new LambdaQueryWrapper<ProductSku>()
                            .eq(ProductSku::getProductId, product.getId()));

            // 为每个SKU创建独立的VO对象
            for (ProductSku sku : productSkus) {
                ProductSkuVo productSkuVo = new ProductSkuVo();
                productSkuVo.setCategory1Id(product.getCategory1Id());
                productSkuVo.setCategory2Id(product.getCategory2Id());
                productSkuVo.setId(sku.getId());
                productSkuVo.setSkuName(sku.getSkuName());
                productSkuVo.setPrice(sku.getPrice());
                productSkuVo.setSaleNum(sku.getSaleNum());
                productSkuVo.setThumbImg(sku.getThumbImg());
                productSkuVoList.add(productSkuVo);
            }
        }
        return productSkuVoList;
    }


    /**
     * 根据 SKU ID 获取商品 SKU 信息
     * 该方法采用缓存优先策略，首先尝试从 Redis 缓存中获取数据：
     * - 如果缓存命中，则直接返回缓存数据；
     * - 如果未命中，则通过分布式锁机制访问数据库，并将结果写入缓存；
     * - 使用 Lua 脚本确保锁的安全释放，防止误删其他线程持有的锁；
     * - 当获取锁失败时，通过递归调用实现自旋重试机制。
     *
     * @param skuId 商品 SKU ID
     * @return ProductSku 商品 SKU 对象，若不存在则返回 null
     */
    @Override
    public ProductSku getSkuInfoById(Long skuId) {
        // 构造缓存键
        String dataKey = "sku:info:" + skuId;

        // 尝试从 Redis 缓存中获取商品 SKU 信息
        ProductSku productSku = (ProductSku) redisTemplate.opsForValue().get(dataKey);
        if (productSku != null) {
            return productSku;
        }

        // 构造分布式锁的键名和锁值
        String lockName = dataKey + ":lock";
        String lockValue = UUID.randomUUID().toString();

        // 尝试获取分布式锁（使用 SETNX 命令）
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(lockName, lockValue, 5, TimeUnit.SECONDS);
        if (flag) {
            try {
                // 获取锁成功后，查询数据库中的商品 SKU 信息
                productSku = this.getById(skuId);

                // 设置随机过期时间以防止缓存雪崩（3600~4200秒之间）
                int ttl = 3600 + new Random().nextInt(600);

                // 将查询到的数据写入 Redis 缓存并设置过期时间
                redisTemplate.opsForValue().set(dataKey, productSku, ttl, TimeUnit.SECONDS);

                // 返回数据库查询结果
                return productSku;
            } finally {
                // 使用Lua脚本安全地释放分布式锁
                String script = """
                        if redis.call("get",KEYS[1]) == ARGV[1]
                        then
                            return redis.call("del",KEYS[1])
                        else
                            return 0
                        end
                        """;
                RedisScript<Boolean> booleanRedisScript = new DefaultRedisScript<>(script, Boolean.class);
                redisTemplate.execute(booleanRedisScript, Arrays.asList(lockName), lockValue);
            }
        } else {
            // 获取锁失败，记录日志并进行自旋重试
            log.error("获取锁失败：{}，再次尝试自旋", Thread.currentThread().getName());
            return this.getSkuInfoById(skuId);
        }
    }


    /**
     * 根据skuId查询sku价格信息
     * 该方法通过传入的skuId查询对应的商品sku记录，然后将查询到的价格信息封装到skuPriceVo对象中返回
     *
     * @param skuId SKU的唯一标识ID
     * @return skuPriceVo 包含SKU价格信息的对象，如果未找到对应记录则返回null
     */
    @Override
    public skuPriceVo selectSkuPriceById(Long skuId) {
        // 构建查询条件：根据skuId查询，并只选择价格和id字段
        LambdaQueryWrapper<ProductSku> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(ProductSku::getId, skuId).select(ProductSku::getPrice, ProductSku::getId);
        // 执行查询，获取单条记录
        ProductSku productSku = baseMapper.selectOne(queryWrapper);
        // 如果查询到数据，则封装成VO对象返回
        if (productSku != null) {
            skuPriceVo priceVo = new skuPriceVo();
            priceVo.setSkuId(productSku.getId());
            priceVo.setPrice(productSku.getPrice());
            return priceVo;
        }
        // 如果未查询到数据，返回null
        return null;
    }


    /**
     * 封装商品规格参数跟SkuId映射关系Map
     * 该方法根据商品ID查询其所有的SKU信息，并将SKU的规格参数作为key，SKU ID作为value封装成Map返回
     *
     * @param productId 商品ID
     * @return Map<String, Long> 规格参数与SKU ID的映射关系，key为规格参数，value为对应的SKUID
     */
    @Override
    public Map<String, Long> getSkuSpecValue(Long productId) {
        // 初始化结果Map
        Map<String, Long> specMap = new HashMap<>();
        // 根据商品ID查询所有相关的SKU信息，只获取ID和规格参数字段
        List<ProductSku> productSkuList = this.list(new LambdaQueryWrapper<ProductSku>()
                .eq(ProductSku::getProductId, productId)
                .select(ProductSku::getId, ProductSku::getSkuSpec)
        );
        // 如果查询到SKU数据，则遍历封装成Map
        if (!CollectionUtils.isEmpty(productSkuList)) {
            for (ProductSku productSku : productSkuList) {
                specMap.put(productSku.getSkuSpec(), productSku.getId());
            }
        }
        return specMap;
    }


    /**
     * 批量获取商品SKU价格
     * 该方法接收一个SKU ID列表，并批量查询对应的商品SKU价格信息，返回一个包含SKU ID和价格信息的列表
     *
     * @param skuIdList SKU ID列表
     * @return List<skuPriceVo> 包含SKU ID和价格信息的列表
     */
    @Override
    public skuPriceVo getSkuPrice(Long skuId) {
        ProductSku productSku = baseMapper.selectOne(new LambdaQueryWrapper<ProductSku>()
                .eq(ProductSku::getId, skuId).select(ProductSku::getPrice, ProductSku::getId));
        if (productSku != null) {
            skuPriceVo priceVo = new skuPriceVo();
            priceVo.setSkuId(productSku.getId());
            priceVo.setPrice(productSku.getPrice());
            return priceVo;
        }
        return null;
    }


    /**
     * 批量获取商品SKU价格
     * 该方法接收一个SKU ID列表，并批量查询对应的商品SKU价格信息，返回一个包含SKU ID和价格信息的列表
     *
     * @param skuIdList SKU ID列表
     * @return List<skuPriceVo> 包含SKU ID和价格信息的列表
     */
    @Override
    public List<skuPriceVo> getSkuPriceList(List<Long> skuIdList) {
        List<ProductSku> productSkuList =
                this.list(new LambdaQueryWrapper<ProductSku>().in(ProductSku::getId, skuIdList).select(ProductSku::getPrice,
                ProductSku::getId));
        if (!CollectionUtils.isEmpty(productSkuList)) {
            return productSkuList.stream().map(productSku -> {
                skuPriceVo priceVo = new skuPriceVo();
                priceVo.setSkuId(productSku.getId());
                priceVo.setPrice(productSku.getPrice());
                return priceVo;
            }).collect(Collectors.toList());
        }
        return null;
    }
}






